#!/usr/bin/env python
# -*- coding: utf-8 -*-
import re
import os
import subprocess
import errno
import time
import sys
import locale

isNumber = lambda x: re.compile(r"^[-+]?\d+$").match(x)

#:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440554
#:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:
PIPE = subprocess.PIPE
if subprocess.mswindows:
    try:
        from win32file import ReadFile, WriteFile
        from win32pipe import PeekNamedPipe
        import msvcrt
    except:
        raise "Sous WINDOWS : Installer PyWin32 (http://sourceforge.net/project/showfiles.php?group_id=78018&package_id=79063)"
else:
    import select
    import fcntl


class Popen(subprocess.Popen):
    def recv(self, maxsize=None):
        return self._recv('stdout', maxsize)

    def recv_err(self, maxsize=None):
        return self._recv('stderr', maxsize)

    def send_recv(self, input='', maxsize=None):
        return self.send(input), self.recv(maxsize), self.recv_err(maxsize)

    def get_conn_maxsize(self, which, maxsize):
        if maxsize is None:
            maxsize = 1024
        elif maxsize < 1:
            maxsize = 1
        return getattr(self, which), maxsize

    def _close(self, which):
        getattr(self, which).close()
        setattr(self, which, None)

    if subprocess.mswindows:
        def send(self, input):
            if not self.stdin:
                return None

            try:
                x = msvcrt.get_osfhandle(self.stdin.fileno())
                (errCode, written) = WriteFile(x, input)
            except ValueError:
                return self._close('stdin')
            except (subprocess.pywintypes.error, Exception), why:
                if why[0] in (109, errno.ESHUTDOWN):
                    return self._close('stdin')
                raise

            return written

        def _recv(self, which, maxsize):
            conn, maxsize = self.get_conn_maxsize(which, maxsize)
            if conn is None:
                return None

            try:
                x = msvcrt.get_osfhandle(conn.fileno())
                (read, nAvail, nMessage) = PeekNamedPipe(x, 0)
                if maxsize < nAvail:
                    nAvail = maxsize
                if nAvail > 0:
                    (errCode, read) = ReadFile(x, nAvail, None)
            except ValueError:
                return self._close(which)
            except (subprocess.pywintypes.error, Exception), why:
                if why[0] in (109, errno.ESHUTDOWN):
                    return self._close(which)
                raise

            if self.universal_newlines:
                read = self._translate_newlines(read)
            return read

    else:
        def send(self, input):
            if not self.stdin:
                return None

            if not select.select([], [self.stdin], [], 0)[1]:
                return 0

            try:
                written = os.write(self.stdin.fileno(), input)
            except OSError, why:
                if why[0] == errno.EPIPE: #broken pipe
                    return self._close('stdin')
                raise

            return written

        def _recv(self, which, maxsize):
            conn, maxsize = self.get_conn_maxsize(which, maxsize)
            if conn is None:
                return None

            flags = fcntl.fcntl(conn, fcntl.F_GETFL)
            if not conn.closed:
                fcntl.fcntl(conn, fcntl.F_SETFL, flags| os.O_NONBLOCK)

            try:
                if not select.select([conn], [], [], 0)[0]:
                    return ''

                r = conn.read(maxsize)
                if not r:
                    return self._close(which)

                if self.universal_newlines:
                    r = self._translate_newlines(r)
                return r
            finally:
                if not conn.closed:
                    fcntl.fcntl(conn, fcntl.F_SETFL, flags)

message = "Other end disconnected!"

def recv_some(p, t=.1, e=1, tr=5, stderr=0):
    if tr < 1:
        tr = 1
    x = time.time()+t
    y = []
    r = ''
    pr = p.recv
    if stderr:
        pr = p.recv_err
    while time.time() < x or r:
        r = pr()
        if r is None:
            if e:
                raise Exception(message)
            else:
                break
        elif r:
            y.append(r)
        else:
            time.sleep(max((x-time.time())/tr, 0))
    return ''.join(y)

def send_all(p, data):
    while len(data):
        sent = p.send(data)
        if sent is None:
            raise Exception(message)
        data = buffer(data, sent)
#:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:


"""
+----[ Commandes de l'interface à distance ]
|
| add XYZ  . . . . . . . . . ajoute XYZ à la playlist
| enqueue XYZ  . . . . . . . queue XYZ to playlist
| playlist . . . . . . . . . . . .afficher les éléments de la playlist
| play . . . . . . . . . . . . . jouer
| stop . . . . . . . . . . . . . arrêter
| next . . . . . . . . . . . . élément suivant
| prev . . . . . . . . . . . . élément précédent
| goto . . . . . . . . . . . . . aller à l'élément X
| clear . . . . . . . . . . . . .vider la liste de lecture
| status . . . . . . . . . . . .afficher les informations
| title [X] . . . . . . . . . .  afficher le titre courant ou sauter à un titre
| title_n  . . . . . . . . . . . .titre suivant dans l'item courant
| title_p  . . . . . . . . . . . .titre précédent
| chapter [X]  . . . . . .  modifier/afficher le chapitre
| chapter_n . . . . . . . . chapitre suivant
| chapter_p . . . . . . . . chapitre précédant
|
| seek X . . . . . . . . . . .se déplacer, en secondes, ex. « seek 12 »
| pause  . . . . . . . . . . .basculer la pause
| fastforward  . . . . . .  avance rapide
| rewind . . . . . . . . . .  rembobiner
| play . . . . . . . . . . . .  jouer plus vite
| slower . . . . . . . . . . .  jouer plus lentement
| normal . . . . . . . . . . .  jouer à vitesse normale
| f [on|off]. . . . . . . . . basculer le mode plein-écran
| info . . . . . . . . . . . . . informations sur le flux courant
| get_time . . seconds elapsed since stream's beginning
| is_playing . .  1 si un élément est joué, 0 sinon
| get_title . . .  the title of the current stream
| get_length . .  the length of the current stream
|
| volume [X] . . . . . . . .modifier/afficher le volume
| volup [X]  . . . . . . . . augmenter le volume de X
| voldown [X]  . . . . . . .diminuer le volume de X
| adev [X] . . . . . . . . . modifier/afficher le périphérique audio
| achan [X] . . . . . . . . modifier/afficher les canaux audios
| atrack [X] . . . . . . . . . set/get audio track
| vtrack [X] . . . . . . . . . set/get video track
| vratio [X]  . . . . . set/get video aspect ratio
| vcrop [X]  . . . . . . . . .  set/get video crop
| vzoom [X]  . . . . . . . . .  set/get video zoom
| strack [X] . . . . . . . set/get subtitles track
| menu [on|off|up|down|left|right|select] utiliser le menu
|
| marq-marquee CHA . . écrit CHA sur la vidéo
| marq-x X . . . . . . . . .décalage du texte depuis la gauche
| marq-y Y . . . . . . . . . décalage du texte depuis le haut
| marq-position # . . . contrôle la position relative du texte
| marq-color # . . . . . .  couleur du texte, RGB
| marq_opacity #. . . . . opacité du texte
| marq-timeout T . . . . .disparition du texte, en ms
| marq-size # . . . . . . . .taille du texte, en pixels
|
| time-format CHAINE . . écrit l'heure selon CHAINE
| time-x X . . . . . . . . .décalage de l'heure depuis la gauche
| time-y Y . . . . . . . . .  décalage de l'heure depuis le haut
| time-position # . . . . .  position relative de l'heure
| time-color # . . . . . . .  couleur de l'heure, RGB
| time-opacity # . . . . . opacité de l'heure
| time-size # . . . . . . .  taille de l'heure, en pixels
|
| logo-file CHAÎNE. . . fichier de logo à incruster
| logo-x X . . . . . . . . . .décalage du logo depuis la gauche
| logo-y Y . .  . . . . . . . décalage du logo depuis le haut
| logo-position # . . . . . position relative du logo
| logo-transparency #. .transparence du logo
|
| mosaic-alpha # . . . . . transparence mosaïque
| mosaic-height #. . . . .hauteur mosaïque
| mosaic-width #  . . . . largeur mosaïque
| mosaic-xoffset # . . . .décalage de la mosaïque depuis la gauche
| mosaic-yoffset # . . . .décalage de la mosaïque depuis le haut
| mosaic-align 0.2,4.6,8.10. .alignement de la mosaïque
| mosaic-vborder # . . . .limite verticale
| mosaic-hborder # . . . .limite horizontale
| mosaic-position {0=auto,1=fixe} . . . .position
| mosaic-rows #   . . .  nombre de rangées
| mosaic-cols #  . . .. . nombre de colonnes
| mosaic-keep-aspect-ratio {0,1} . . . .étirement
|
| check-updates [newer] [equal] [older]
| [undef] [info] [source] [binary] [plugin]
|
| help . . . . . . . . . . . . . ce message d'aide
| longhelp . . . . . . . . .message d'aide plus long
| logout . . . . . . . . . . . quitte l'interface sans fermer vlc
| quit . . . . . . . . . . . .  quitte VLC
|
+----[ fin de l'aide ]"""



class PilotVlc(object): # SINGLETON !!!!
    exe=u"/usr/bin/vlc"
    
    ipFbx="freeplayer.freebox.fr"   # ip destinataire du flux
    debug=False
    
    #ipFbx="127.0.0.1"   # ip destinataire du flux
    #debug=True
    
    def __new__(type):
        if not '_the_instance' in type.__dict__:
            os.system("killall vlc")
            type._the_instance = object.__new__(type)
            self=type._the_instance

            if sys.platform[:3] == 'win':
                cmd="cmd"
            else:
                cmd="sh"

            self.__pipe = Popen(cmd, stdin=PIPE, stdout=PIPE)
            time.sleep(0.3)
            recv_some(self.__pipe)
            
        return type._the_instance


    def __cmd(self,cmd,withStatusChange=False):
        if self.__pipe:
            assert type(cmd)==unicode
            encoding = locale.getpreferredencoding()
            if sys.platform[:3] == 'win':
                tail = u'\r\n'
            else:
                tail = u'\n'
            send_all(self.__pipe, (cmd + tail).encode(encoding) )
            time.sleep(0.2)
            buf= recv_some(self.__pipe).decode(encoding)
            list=[]
            for i in buf.strip().split(u"\r\n"):
                if i.startswith(u"status change: "):
                    if not withStatusChange:
                        continue
                if i.startswith(u"Interface de commande "):
                    continue
                list.append(i)
            if PilotVlc.debug:
                print "::",[cmd,],"::",buf.strip().split(u"\r\n")
            return list


    def __del__(self):
        self.stop()
    

    def isPlaying(self):
        """ retourne True ou False suivant que VLC est lancé et joue qqchose
            1 : play
            2 : pause
            0 : stop    (vlc n'est pas lance)
        """
        ll = self.__cmd(u"is_playing")
        if ll:
            if u"1" in ll:
                if u"status change: ( play state: 1 )" in self.__cmd(u"status",True):
                    return 1
                else:
                    return 2
            else:
                return 0
        else:
            return 0
        
    def __getLength(self):
        """ renvoi la longueur en sec du media actuel """
        buf=self.__cmd(u"get_length")
        if buf:
            nbs=[int(i) for i in buf if isNumber(i)]
            if nbs:
                return nbs[0]
        
    def __getTime(self):
        """ renvoi le nb de sec passé du media actuel """
        buf=self.__cmd(u"get_time")
        if buf:
            nbs=[int(i) for i in buf if isNumber(i)]
            if nbs:
                return nbs[0]

    def getPercent(self):
        length = self.__getLength()
        time = self.__getTime()
        if length:
            return (time * 100)/length
        
    def getTitle(self):
        """ renvoi le titre du media actuel """
        ll=self.__cmd(u"get_title")
        if ll:
            for i in ll:
                if os.path.isfile(i):
                    return i
        
    def getVolume(self):
        """ retourne le pourcentage du volume """
        l=self.__cmd(u"volume",True)
        if l: #status change: ( audio volume: 1000 )
            for i in l:
                g=re.match("status change: \( audio volume: (\d+) \)",i)
                if g:
                    v=int(g.group(1))
                    return (v*100)/1024

    def setVolume(self,v):
        """ met le pourcentage du volume """
        assert v in range(0,101)
        self.__cmd(u"volume %d" % ((v*1024)/100))
    
    def playVideo(self,file):
        assert type(file)==unicode
        assert '"' not in file
        assert os.path.isfile(PilotVlc.exe)
        
        cmds=[
            PilotVlc.exe,
                "-I", "rc",
                '--rc-fake-tty',
                '--no-quiet',
                '--no-advanced',
                """--sout="#std" """,
                "--sout-standard-access=udp",
                "--sout-standard-mux=ts",
                "--sout-standard-dst=%s:1234" % PilotVlc.ipFbx,
                "--sout-ts-pid-video=68",
                "--sout-ts-pid-audio=69",
                "--sout-ts-pid-spu=70",
                "--sout-ts-pcr=80",
                "--sout-ts-dts-delay=400",
                "--sout-transcode-maxwidth=720",
                "--sout-transcode-maxheight=576",
                "--play-and-stop",
                "--no-playlist-autostart",
                ]
        cmd=unicode(u" ".join(cmds))
        cmd+=(u""" "%s" """ % file)
        print (cmd,)
        
        if self.isPlaying():
            self.stop()
        #
        #if not self.isPlaying():            
        #    self.__connect()
            
        self.__cmd(cmd)
        time.sleep(1)

    def play(self):
        self.__cmd(u"play")

    def pause(self):
        self.__cmd(u"pause")
        
    def stop(self): # kill VLC
        if self.isPlaying():
            send_all(self.__pipe, "quit\n" )
            time.sleep(0.2)
        
    def seek(self,p):
        """ seek en pourcentage"""
        assert p in range(0,101)
        length = self.__getLength()
        val = int( (p*length) / 100 )
        self.__cmd(u"seek %d" % val)

    def getInfo(self):
        return self.__cmd(u"info")
    
    def cmd(self,m,s=False):    # TEST ONLY
        return self.__cmd(m,s)
    
if __name__ == '__main__':
    #TODO: mettre d'autres fichiers
    media=u"/media/d/divx/brice de nice.avi"
    media2=u"/media/d/divx/L'Iznôgoud.FRENCH.DVDRiP.XViD-iD.avi"
    media2=u"/home/manatlan/.democracy/Movies/3491230.mp4-35f86c5c3754328f919a780fddc3cb4b12c61e4.mp4"
    PilotVlc.debug=True
    
    #TODO: unittest !
    try:
        p=PilotVlc()
        assert p.isPlaying()== 0
        p.playVideo(media2)
        assert p.isPlaying()== 1
        p.pause()
        assert p.isPlaying()==2
        
        assert p.getVolume()==None
        assert p.getTitle()==media2
        
        p.seek(40)                          # seek pas pendant la pause
        assert p.getPercent()==0          # recupere donc etat actuel du seek

        p.play()                            # play fait rien
        assert p.isPlaying()==2
       
        p.pause()                           # sort de la pause
        assert p.isPlaying()==1
        time.sleep(1)
        

  
        p.seek(50)                          # test seek
        
        assert p.getPercent()==50
        
        p.setVolume(100)                    # test volume
        assert p.getVolume()==100
        
        assert p.isPlaying()== 1
        p.stop()

        assert p.isPlaying()== 0,p.isPlaying()
        assert p.getTitle()==None

        pp=PilotVlc()
        assert pp.isPlaying()== 0
        pp.playVideo(media)
        assert pp.isPlaying()== 1
        assert pp.getTitle()==media

        pp.pause()              # pause
        assert pp.isPlaying()==2
        pp.pause()              # play
        assert pp.isPlaying()==1

        pp.pause()
        assert pp.isPlaying()==2

        pp.playVideo(media2)
        assert pp.isPlaying()==1
        assert pp.getTitle()==media2
        assert pp.getPercent()==0

        ppp=PilotVlc()                  #recupere l'instance en cours, en train de jouer
        assert ppp.isPlaying()== 1
        print ppp.cmd(u"info")
        ppp.stop()

        pppp=PilotVlc()                  #recupere l'instance en cours, ne jouant pas
        assert pppp.isPlaying()== 0

        assert pppp.getVolume()==None
        assert pppp.getTitle()==None
        assert pppp.getPercent()==None
    finally:
        p=PilotVlc()
        p.stop()